//      priv_leitor.c
// 		Sistemas Operacionais 1  da Universidade Federal de Goias
//		Problema dos leitores-escritores como privilegio para os leitores
//      Autor: Arthur Henrique Guimaraes de Oliveira
//		Autor: Danilo Inácio de Souza Resende
//      
//      Este programa esta Disponivel sobre a Lincença GNU General Public License v3
//		no seguinte endereço: https://code.google.com/p/so-lving/     

 
#include <pthread.h>
#include <semaphore.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>

#include "common.h"

// ## ----------------------------------------  FIM INCLUDES --------------------------------------------------------##

/* Variaveis Globais
 * mutex => semáforo que controla acesso à variável "leitores_num"
 * attr => 
 * semaforo => semáforo que controla acesso à variavel "estoque".
 * estoque => Recurso compartilhado por leitores e escritores
 * estoque_index => 
 * leitores_num => numero de leitores com acesso ao banco de dados
 * log_file =>
 * cur_targs => 
 * *status => 
 * rc =>
*/

pthread_mutex_t mutex; 
pthread_attr_t attr;
sem_t   semaforo;
char    estoque[T_LIMIT];  
int     estoque_index;  
int     leitores_num = 0; 
FILE    *log_file;
targs   cur_targs[T_LIMIT];
void    *status;
int     rc;
int     qt_leit;
int     qt_esc;

// ## ----------------------------------------  FIM GLOBAIS --------------------------------------------------------##

/* THREADS:
 * Leitor => Essa funçao simula a forma como um leitor do banco de dados age.
 * Escritor => Essa funçao simula a forma como um escritor do banco de dados age.
*/

void *Leitor(void* arg){
    targs* tparams = (targs*) arg;
	
    msg_ini(tparams, log_file);

    //Tenta acesso à variável que conta o número de leitores lendo/esperando para ler o base de dados.
    // Aqui mutex e "trancado" pela chamada do pthread_mutex_lock(). 
    // Caso mutex ja tenha sido "trancado" a chamada e bloqueada ate que a variavel mutex fique disponivel.
    pthread_mutex_lock(&mutex); 
    
    //Thread se junta aos leitores incrementando contagem de leitores.
    leitores_num += 1; 
    
    if (leitores_num == 1){ //Caso a thread leitora seja a primeira a tentar o acesso ao banco
        sem_wait(&semaforo); //deve entao bloquear o acesso ao banco
    }
    
    pthread_mutex_unlock(&mutex);//libera acesso à variável contadora
    acessa_area_compartilhada(tparams, log_file, estoque, estoque_index);

    pthread_mutex_lock(&mutex); //adquiri acesso à variável contadora
    leitores_num -= 1; //decrementa contagem de leitores com intenção de ler
    
    if (leitores_num == 0){ //se for o último leitor a ler a base, libera acesso a ela
        sem_post(&semaforo);
    }
    pthread_mutex_unlock(&mutex);//libera acesso à variável contadora
    
    pthread_exit(NULL);
}

void *Escritor(void* arg){
    targs* tparams = (targs*) arg;
	
    msg_ini(tparams, log_file);
    
    sem_wait(&semaforo); //pede acesso à base de dados e bloqueia caso haja leitores ou outro escritor com acesso a ela
    acessa_area_compartilhada(tparams, log_file, estoque, &estoque_index); // escreve a nova mensagem no banco
    sem_post(&semaforo); //libera acesso ao banco
    
    pthread_exit(NULL);
}

// ## ----------------------------------------  FIM THREADS --------------------------------------------------------##

/* FUNÇÃO PRINCIPAL
 *  
*/

int main(int argc, char* argv[]){
 
	int i;
	
    qt_leit = qt_esc = estoque_index = 0;
    log_file = fopen("log.txt", "w");
    
    for(i=0; i<T_LIMIT; i++)
        estoque[i] = ' ';
    
    pthread_mutex_init(&mutex, NULL); //Semáforo que controla "contador_leitores" iniciado como NULL
    sem_init(&semaforo,0,1); //Semáforo que controla acesso à base de dados iniciado como setado
	
	pthread_t leit[T_LIMIT]; //Criado vetor de threads leitoras
    pthread_t esc[T_LIMIT]; // Criado vetor de threads escritoras
    
    if(argc > 1){
        if(strcmp(argv[1], "--auto") == 0){
            int rnd_t;

            for(i=0; i<T_LIMIT; i++){
                rnd_t = rand() % 2;
                cur_targs[i].sleept = rand() % 11;
                
                switch(rnd_t){
                    case GERENTE:
                        cur_targs[i].id     = qt_leit;
                        cur_targs[i].type   = GERENTE;
                        printf("Leitor   %d foi criado, e sera executado em %d segundo(s).\n", cur_targs[i].id, cur_targs[i].sleept);
                        pthread_create(&leit[qt_leit], &attr, Leitor, (void*) &cur_targs[i]);
                        qt_leit++;
                        break;

                    case PRODUTOR:
                        cur_targs[i].id     = qt_esc;
                        cur_targs[i].type   = PRODUTOR;
                        printf("Escritor %d foi criado, e sera executado em %d segundo(s).\n", cur_targs[i].id, cur_targs[i].sleept);
                        pthread_create(&esc[qt_esc], &attr, Escritor, (void*) &cur_targs[i]);
                        qt_esc++;
                        break;
                }
            }
        }
        // Se o argumento passado for errado
        else {
            printf("%s", how_to_use());
        }
    }
    // Caso o usuário não passe parâmetros ele terá controle sobre a criação
    // das threads
    else {
        int line_width = 20;
        char line[line_width + 1], *token, args[line_width][line_width];
        int error, nargs;

        // Mensagem básica
        printf("Abra um novo terminal e execute \"$tail -f log.txt\" para ver o log das ações\n");
        for(i=0; i<T_LIMIT; i++){
            error = nargs = 0;
            args[0][0] = args[1][0] = '\0';

            printf("%s", menu());
            fgets(line, line_width, stdin);
            line[strlen(line) - 1] = '\0';

            if(strcmp(line, "exit") == 0){
                break;
            }

            token = strtok(line, " ");
            while(token != NULL){
                strcpy(args[nargs++], token);
                token = strtok(NULL, " ");
            }
            
            cur_targs[i].id = i;
            if(strcmp(args[0], "p") == 0) {
                cur_targs[i].type = PRODUTOR;
            }
            else if(strcmp(args[0], "g") == 0) {
                cur_targs[i].type = GERENTE;
            }
            else {
                error = 1;
            }

            if(args[1][0] != '\0'){
                cur_targs[i].sleept = atoi(args[1]);
                if(cur_targs[i].sleept < 0 || cur_targs[i].sleept > 10) {
                    error = 1;
                }

            }
            else {
                cur_targs[i].sleept = rand() % 11;
            }

            if(error) {
                printf("Erro, use o programa segundo o menu:");
                continue;
            }

            switch(cur_targs[i].type){
                case GERENTE:
                    printf("Um leitor foi criado, e sera executado em %d segundo(s).\n", cur_targs[i].sleept);
                    pthread_create(&leit[qt_leit], &attr, Leitor, (void*) &cur_targs[i]);
                    qt_leit++;
                    break;

                case PRODUTOR:
                    printf("Um escritor foi criado, e sera executado em %d segundo(s).\n", cur_targs[i].sleept);
                    pthread_create(&esc[qt_esc], &attr, Escritor, (void*) &cur_targs[i]);
                    qt_esc++;
                    break;
            }
            fflush(stdin);

        }

    }
    printf("saindo..\n");

// Fazer com que o fim do programa ocorra quando as threads forem finalizadas
    for(i = 0; i < qt_leit; i++)
		pthread_join(leit[i], NULL);

	for(i = 0; i < qt_esc; i++)
		pthread_join(esc[i], NULL);

    fprintf(log_file, "Fim da execução\n");
    fclose(log_file);

	return 0;	 
}
